<template>
  <div class="container">
    <!--头部筛选 start-->
    <div class="flex-row justify-between p_b10">
      <div>
        <el-input size="small" v-model="params.search" placeholder="分类名称" class="m_r10 width200"></el-input>
        <el-button size="small" type="primary" icon="el-icon-search" @click="onPageChange(1)"></el-button>
      </div>
      <div>
        <el-button size="small" type="primary" @click="toggleEditorModal(true)">创建分类</el-button>
      </div>
    </div>
    <!--头部筛选 end-->

    <!--列表数据 start-->
    <el-table :data="tableData" border size="mini">
      <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
      <el-table-column prop="name" label="分类名称"></el-table-column>
      <el-table-column prop="level" label="级别"></el-table-column>
      <el-table-column prop="sort" label="排序"></el-table-column>
      <el-table-column label="状态" align="center">
        <template #default="{row}">
          <el-tag
              size="mini"
              :type="row.status === 1? 'success':'warning'">
            {{ row.status === 1 ? '启用' : '禁用' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column :min-width="180" prop="created_at" label="创建时间"></el-table-column>

      <el-table-column label="操作" width="180" align="center">
        <template #default="{row}">
          <el-button type="text" icon="el-icon-edit" @click="toggleEditorModal(true,row)" size="mini">编辑</el-button>
          <el-button type="text" icon="el-icon-paperclip" @click="toggleStatus(row)" size="mini" v-if="row.status===1">
            下架
          </el-button>
          <el-button type="text" icon="el-icon-paperclip" @click="toggleStatus(row)" size="mini" v-else>上架</el-button>
          <el-button type="text" icon="el-icon-delete" @click="onDestroy(row)" size="mini">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!--列表数据 end-->


    <!--分页组件 start-->
    <div class="pagination">
      <el-pagination
          background layout="total, prev, pager, next"
          :current-page="pageMeta.current_page"
          :page-size="pageMeta.per_page" :total="pageMeta.total"
          @current-change="onPageChange"></el-pagination>
    </div>
    <!--分页组件 end-->


    <!--分类编辑、新增弹框 start-->
    <el-dialog
        v-model="formData.show"
        :title="formData.data.id>0?'编辑分类':'新增分类'"
        width="50%">
      <!--formRefName是一个ref()包装的数据,并且ref属性前面不需要v-bind:指令修饰-->
      <!--rules也可以使用vue2中的data的属性-->
      <el-form
          ref="formRefName"
          :rules="rules"
          :model="formData.data" label-width="120px">
        <el-form-item label="上级分类">
          <el-select
              :disabled="formData.data.id>0"
              v-model="formData.data.parent_id"
              placeholder="please select your zone">
            <!--注：category 来源：computed中的vuex state的属性映射-->
            <el-option label="一级分类" :value="0"></el-option>
            <el-option :label="item.name" :value="item.id" v-for="item of category" :key="item.id"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="分类名称" prop="name" required>
          <el-input v-model="formData.data.name" placeholder="请填写分类名称"></el-input>
        </el-form-item>
        <el-form-item label="排序">
          <el-input-number v-model="formData.data.sort" placeholder="排序"></el-input-number>
        </el-form-item>
        <el-form-item label="状态">
          <el-radio-group v-model="formData.data.status">
            <el-radio :label="1">启用</el-radio>
            <el-radio :label="2">禁用</el-radio>
          </el-radio-group>
        </el-form-item>

      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button size="small" @click="toggleEditorModal(false)">取消</el-button>
          <el-button size="small" type="primary" @click="onSubmit()">提交</el-button>
        </div>
      </template>
    </el-dialog>
    <!--分类编辑、新增弹框 end-->

  </div>
</template>

<script>
import {defineComponent, reactive, ref, toRaw, watch} from "vue"
import {usePageMeta} from "../../hooks/usePageTableData";
import Category from "../../api/modules/Category";
import {deepClone} from "../../utils/helpers";
import Toast from "../../utils/Toast";
import {mapState, useStore} from "vuex";

export default defineComponent({
  name: "Category",
  props: {
    testValue: [Number, String]
  },
  data() {
    return {
      testData: '这个数据无法在setup中获取到',
      rules: {
        name: [
          {required: true, message: "请填写分类名称"}
        ]
      },
    }
  },
  // ......这是一千行代码.....
  methods: {
    testMethod() {
      // 1、这是vue2的写法这里依然兼容，可以使用testData
      // 2、setup中无法调用该方法
      // 3、vue2的写法是分散的、逻辑和数据或者其它分离，当该页面很大很长、有几千行代码时弊端很明显。
      //    比如该方法使用到了this.testData。如果中间隔着一千行代码，找起来是很麻烦的
      console.log(this.testData)

      // 事件触发（setup中使用ctx.emit()触发）
      this.$emit('onTest', "参数");
    }
  },
  computed: {
    // vuex中的state数据还是建议使用vue2的写法更高效（可以减少很多代码关联）
    // src/store/modules/category.js 中的state.category 映射为一个计算属性
    ...mapState("category", [
      "category",
    ])
  },
  /**
   * 1、setup()一般在created()之前执行
   * 2、setup()中的this===undefined 也就是setup中无法使用vue2版本语法中的数据比如data、methods
   * @param props 这是props参数 目前组件为{testValue?:number|string}
   * @param emit:{attrs,slots,emit,expose} 第二个参数是一个上下文SetupContext对象，开发者常用到的是emit()函数
   * @returns {any}
   */
  setup(props, {emit}) {
    // emit('onTest',"参数"); 这是emit()的用法。vue2参考testMethod()里面的写法
    /**
     * 注：
     * 1、响应数据：类似vue2的data数据。即表示vue实例能监听到变化的数据，当数据发生变化时、依赖于该数据的部位均会发生相应的响应
     * 2、ref()、reactive()的区别
     *   - ref({}) ==== reactive({value:{}});
     *   - 用法：
     *      const refData= ref({value:""});
     *      const reactiveData=reactive({value:""});
     *   - 1）.在setup范围内使用ref返回的值实例：refData.value.value="赋值一个新的数据"。
     *   - 2）.在setup范围内使用reactive返回的值实例：reactiveData.value="赋值一个新的据"。
     *   - 3）.在template模板中使用没有任何区别：{{refData.value}}   {{reactiveData.value}}
     * 3、什么时候使用ref()？
     *   - 仅当需要对整体数据做同一替换赋值的时候。例如下面的分类列表data。每次请求到新数据都会进行 tableData.value=[...];
     * 4、什么时候使用reactive()？
     *   - ref()除外的条件（只需要操作数据中的子项时）。譬如下列的"筛选条件"```params```。每次改变search参数只需 params.search="新的筛选关键词";
     */
    const store = useStore(); // store等效于vue2中的this.$store。通过store可以操作vuex
    const formRefName = ref(null); // 这个是为了设置编辑、提交表单的ref属性。（固定写法）
    const pageHandle = usePageMeta(); // 分页数据（响应式数据）
    const params = reactive({ // 筛选条件（响应式数据）
      search: "",
    });
    const tableData = ref([]); // 分类列表（响应式数据）


    onPageChange(1); // 初始化时直接请求一次分类列表


    // 表单数据
    const formData = reactive({
      show: false,
      data: {
        id: 0,
        parent_id: 0,
        level: 1,
        name: '',
        sort: 0,
        status: 1,
      }
    });

    // 这是原始数据（用于做恢复初始化数据时需要用到的）
    // toRaw()是去掉reactive()的响应式包装器
    // deepClone()方法是做深拷贝，避免旧对象属性的变更影响到新的数据
    const _formData = deepClone(toRaw(formData));

    /**
     * 这是watch的演示（类似vue2中的watch功能）,接受三个参数
     * 1、第一个参数，传入一个匿名函数返回的属性就是需要监听的值，props值也可以监听。更多参数形式请参考：
     *    https://v3.cn.vuejs.org/guide/reactivity-computed-watchers.html#watch
     * 2、第二个参数是一个函数
     * 3、第三个参数是一些额外的属性，通常最常用的是immediate
     */
    watch(() => formData.show, (newVal, oldVal) => {
      console.log(newVal, oldVal)
    }, { // 一些监听属性 （可选参数）
      // immediate:true
    })

    /**
     * 打开关闭编辑框
     * @param show
     * @param data
     */
    function toggleEditorModal(show, data = {}) {
      formData.show = show;
      formData.data = Object.assign({}, _formData.data, data); // 设置表单数据
      if (formRefName.value) { // 重置表单的验证状态
        formRefName.value.resetFields();
      }
      if (show) {
        // 调用src/store/modules/category.js actions->getAll()初始化所有分类。
        // vuex4.* 更多用法参考：https://next.vuex.vuejs.org/zh/index.html
        store.dispatch("category/getAll", true);
      }

    }

    // 提交功能
    function onSubmit() {
      if (!formRefName.value) { // 当前页面未准备好，或者formRefName未设置到el-form表单的ref属性中
        Toast.error('找不到表单')
        return;
      }
      formRefName.value.validate(async (valid) => {
        if (!valid) { // 表单验证不通过
          return;
        }
        // 取表单数据（使用深拷贝deepClone处理后，可便捷的进行数据的加工而不影响到其它原始数据）
        const _formData = deepClone(toRaw(formData)).data;
        try {
          _formData.level = _formData.parent_id > 0 ? 2 : 1;
          let res = {}
          if (_formData.id > 0) { // 编辑操作
            res = await Category.update(_formData)
            onPageChange()
          } else { // 新增操作
            delete _formData.id; // 删除原始数据的id字段（通常不影响，但应该做到严谨）
            res = await Category.create(_formData);
            onPageChange(1)
          }
          Toast.success(res.message || '操作成功');
          toggleEditorModal(false)
        } catch (e) {
          Toast.error(e.message);
        }

      })
    }


    /**
     * 修改状态
     * @param row
     * @returns {Promise<void>}
     */
    async function toggleStatus(row) {
      try {
        const res = await Category.toggleStatus(row.id);
        Toast.success(res.message || '修改状态成功');
        onPageChange(); // 重新请求数据
      } catch (e) {
        console.log(e)
        Toast.error(e.message || '修改状态失败')
      }
    }

    /**
     * 删除分类
     * @param row {any}
     * @returns {Promise<void>}
     */
    async function onDestroy(row) {
      const canDestroy = await Toast.confirm();
      if (!canDestroy) { // 用户点击了取消
        return;
      }
      try {
        const res = await Category.destroy(row.id);
        Toast.success(res.message || '删除成功');
        if (tableData.value.length === 1) { // 仅当剩下最后一条数据时获取第一页数据
          onPageChange(1);
        } else { // 刷新当前页面数据（列表会重新渲染，这是最快捷的方式，新的页面会自动减少已经删除的项目）
          onPageChange();

        }
      } catch (e) {
        console.log(e)
        Toast.error(e.message || '删除失败')
      }
    }

    /**
     * 获取分类列表
     * @param page 获取的页码，不传则表示刷新（获取当前页）
     * @returns {Promise<void>}
     */
    async function onPageChange(page) {
      /**
       * 1、pageMeta用的ref()方法，所以真实的数据再value属性上）
       * 2、这里使用局部变量通过deepClone()克隆一个pageMeta对象是为了保证后续的pageMeta.page = page不会影响到原数据
       */
      const pageMeta = deepClone(pageHandle.pageMeta.value)
      if (page > 0) { // 如果有页码则首先设置页码，否则视为刷新操作
        pageMeta.index = page;
      }
      try {
        const {data} = await Category.lists({
          ...pageMeta, // 设置分页数据主要是使用page参数（
          ...params, // 这是其它筛选条件主要是使用search参数（params用的reactive()，可以直接使用）
        });
        // 由于接口性质，httpStatus!==200的数据返回均走的catch;
        // 所以此处无需再进行code===200的判断。走到这里的都是成功返回数据的

        const {list, meta} = data || {}; // 短路写法，做兜底，仅当data===undefined时不报错
        tableData.value = list || []; // 赋值到列表响应数据（仅当list===undefined时赋值[]保证vue页面不报错）

        // 仅当数据成功时才改变分页数据参数（其实最重要的是改变page属性，表示当前分页数）
        // 上面的pageMeta.page = page;不会改变到原分页参数（保证数据的统一性）
        pageHandle.updatePageMeta(pageMeta.index, meta);

      } catch (e) { // httpStatus!==200
        console.log(e);
      }
    }

    /**
     * 导出数据。setup只有通过return返回的数据才能在template模板中直接使用
     */
    return {
      ...pageHandle,
      params,
      onSubmit,
      formData,
      formRefName,
      toggleEditorModal,
      onDestroy,
      toggleStatus,
      tableData,
      onPageChange,
    }
  }
})
</script>

<style lang="scss" scoped>

</style>
